Introduction
We will will building on our lesson on ggplot101 and ggplot102 which
focused on an overall understanding of the grammar of graphics, basic
syntax, adding data, aesthetic mappings, geoms, facets, scales, labels,
and themes. Today we are going to apply what we learned towards trying
to better understanding our underlying data distributions.
Often, we think about figure generation as the last part of the
scientific process, something you do as you prepare a manuscript for
publication. I hope to convince you that exploring your data, and making
exploratory plots is a critical part of the data analysis and
interpretation process.
Load libraries and data
Before we get started, let’s load our libraries.
library(tidyverse)
Today we are using real research data from my group. We will be
reading in the supplementary data from a paper
written by Michael Dzakovich, and published in The Plant Genome. The
data is present in a Excel worksheet, so we will use the function
read_excel() from the tidyverse (but not core tidyverse)
package readxl. We want to import Supplemental Table 1. You
can indicate which sheet you want to import in the arguments to
read_excel().
alkaloids <- readxl::read_excel("tpg220192-sup-0002-supmat.xlsx",
sheet = "S1 Raw Data Diversity Panel")
knitr::kable(head(alkaloids))
| 7805 |
2018 |
FreEarly18 |
1 |
CULBPT_05_11 |
2K17-7724 |
Cultivated Processing |
USA |
NY |
SLL_processing_2 |
SLL_processing_2 |
SLL |
SLL_processing_NY |
Arid |
40.712800000000001 |
-74.006 |
0.000000 |
0.000000 |
5.726010 |
0.350331 |
6.076341 |
172.66244 |
1.079190 |
86.72742 |
17.831892 |
9.142607 |
114.78111 |
18.902399 |
56.307182 |
1.890053 |
77.099634 |
5.125904 |
10.277325 |
336.8893 |
347.1666 |
3.787979 |
0.924195 |
3.943230 |
8.655404 |
731.5675 |
| 7898 |
2017 |
Fre17 |
2 |
CULBPT_05_11 |
2K9-8584 |
Cultivated Processing |
USA |
NY |
SLL_processing_2 |
SLL_processing_2 |
SLL |
SLL_processing_NY |
Arid |
40.712800000000001 |
-74.006 |
0.000000 |
0.169068 |
0.000000 |
0.000000 |
0.000000 |
55.47329 |
0.000000 |
53.32292 |
13.630697 |
4.841762 |
71.79538 |
3.557348 |
4.107289 |
0.000000 |
7.664637 |
2.905500 |
5.548102 |
199.6694 |
205.2175 |
8.978931 |
1.897850 |
6.794690 |
17.671471 |
360.8969 |
| 7523 |
2018 |
FreLate18 |
2 |
CULBPT_05_11 |
2K17-7724 |
Cultivated Processing |
USA |
NY |
SLL_processing_2 |
SLL_processing_2 |
SLL |
SLL_processing_NY |
Arid |
40.712800000000001 |
-74.006 |
0.135675 |
0.680554 |
5.073552 |
0.000000 |
5.073552 |
123.85835 |
0.000000 |
50.90989 |
6.503939 |
1.368847 |
58.78268 |
3.931461 |
4.123222 |
0.623340 |
8.678023 |
2.185082 |
5.104115 |
259.0177 |
264.1218 |
4.049145 |
0.000000 |
6.749386 |
10.798531 |
474.3143 |
| 7724 |
2017 |
Fre17 |
1 |
CULBPT_05_11 |
2K9-8584 |
Cultivated Processing |
USA |
NY |
SLL_processing_2 |
SLL_processing_2 |
SLL |
SLL_processing_NY |
Arid |
40.712800000000001 |
-74.006 |
0.054300 |
0.497261 |
19.419087 |
0.000000 |
19.419087 |
239.01264 |
0.000000 |
36.02318 |
8.557673 |
7.483933 |
52.06478 |
3.341048 |
16.415426 |
1.057100 |
20.813574 |
0.000000 |
0.000000 |
203.0061 |
203.0061 |
1.678210 |
0.000000 |
2.349633 |
4.027843 |
538.8955 |
| 7427 |
2018 |
FreLate18 |
1 |
CULBPT_05_11 |
2K17-7724 |
Cultivated Processing |
USA |
NY |
SLL_processing_2 |
SLL_processing_2 |
SLL |
SLL_processing_NY |
Arid |
40.712800000000001 |
-74.006 |
0.139454 |
0.553801 |
0.000000 |
0.000000 |
0.000000 |
64.31783 |
0.879435 |
39.91027 |
7.228388 |
3.015298 |
51.03339 |
0.000000 |
3.131685 |
0.000000 |
3.131685 |
0.000000 |
4.054211 |
299.5687 |
303.6229 |
10.146857 |
0.000000 |
4.882339 |
15.029197 |
437.8283 |
| 7854 |
2018 |
FreEarly18 |
2 |
CULBPT_05_11 |
2K17-7724 |
Cultivated Processing |
USA |
NY |
SLL_processing_2 |
SLL_processing_2 |
SLL |
SLL_processing_NY |
Arid |
40.712800000000001 |
-74.006 |
0.049700 |
0.262174 |
3.737579 |
0.000000 |
3.737579 |
68.44913 |
0.000000 |
23.86864 |
13.506299 |
1.456982 |
38.83192 |
4.657902 |
4.259007 |
0.605729 |
9.522638 |
9.832149 |
11.595595 |
459.5205 |
471.1161 |
6.839930 |
0.486236 |
5.595751 |
12.921917 |
614.7233 |
This dataset has 605 observations, with data about different
steroidal alkaloids in the fruits of different tomato germplasm grown in
3 locations across 2 years. There is also some other metadata too.
Geoms for distributions
Often, people use bar charts, representing the height or the length
of the bar as proportional to the average value that it represents.
These charts are sometimes called dynamite plots because they resemble
(when they have an error bar with whisker) those cartoon style dynamite
sticks. Pow!
However, these bar charts, even if you add a standard
deviation/error, really can hide the true distribution of your data, and
for this reason, I and others
hope you don’t select to make them.
Aside: You may be thinking “Jess you asked us to make one of these in
Module 2 homework” and I did but also that was a little different. The
plot I asked you to make shows the number of degrees awarded, a value
for which there really is no distribution. So in that case we are using
a bar plot to show something different than a bar plot which is meant to
show somehow an average/median and distribution.
I hope after today, you see that there is always a better chart type
to make than a bar chart. But I will show you how to make them
anyway.
Before we plot, let’s calculate some summary statistics so we know
what we should expect.
alkaloids %>%
group_by(Class) %>%
summarize(mean_tomatine = mean(Tomatine))
## # A tibble: 5 × 2
## Class mean_tomatine
## <chr> <dbl>
## 1 Cultivated Cherry 235.
## 2 Cultivated Processing 330.
## 3 S. pimpinellifolium 685.
## 4 Wide Cross Hybrid 534.
## 5 Wild Cherry 4928.
# this is wrong but an easy mistake to make
# this is not what we want
alkaloids %>%
ggplot(aes(x = Class, y = Tomatine)) +
geom_col()

Just calling geom_col() does not give us what we want.
Look at the y-axis scale and how out of line this is with our summary
statistics. The reason for this is that geom_col() defaults
to position = "stack" which will just sum the alkaloid
content across all the observations. Even changing to
position = "identity" does not work. This is because we are
plotting a transformation of the data (calculation of the mean) which
these geoms are not doing.
We can calculate manually by generating the summary values and then
piping that into our ggplot call.
alkaloids %>%
group_by(Class) %>%
summarize(mean_tomatine = mean(Tomatine)) %>%
ggplot(aes(x = Class, y = mean_tomatine)) +
geom_col()

An easier way to do this would be just with
stat_summary(), which does not require the calculation of
summary statistic first.
alkaloids %>%
ggplot(aes(x = Class, y = Tomatine)) +
stat_summary(fun = "mean", geom = "bar")

Reordering x-variables
Note in these plots the ordering of the x-axis categories – they are
alphabetical. This is the ggplot default. There are many reasons why
this might not be the most compelling ordering for your data. You may
want to order from lowest to highest mean, or in this case, I want to
order the tomatoes from most cultivated on the left, to most wild on the
right, since this is the prevailing theme of our paper.
We can do this in two ways:
Simply reorder the plot.
# set what the order is
alkaloids_order <- c("Cultivated Processing",
"Cultivated Cherry",
"Wide Cross Hybrid",
"Wild Cherry",
"S. pimpinellifolium")
# plot and re-level within aes()
alkaloids %>%
ggplot(aes(x = factor(Class, levels = alkaloids_order), y = Tomatine)) +
stat_summary(fun = "mean", geom = "bar")

Change the levels of the data so the reordering happens to every plot
in the future.
# what type of variable is class?
class(alkaloids$Class)
## [1] "character"
# convert to factor, and set levels
alkaloids$Class <- factor(alkaloids$Class,
levels = c("Cultivated Processing",
"Cultivated Cherry",
"Wide Cross Hybrid",
"Wild Cherry",
"S. pimpinellifolium"))
alkaloids %>%
ggplot(aes(x = Class, y = Tomatine)) +
stat_summary(fun = "mean", geom = "bar")

My tendency would be to re-level the data if I always want to use the
same order, and just re-level the plot if I only want to do this once or
twice.
A boxplot has the benefit of showing you more than the median and the
standard deviation, so you can better see the true distribution of your
data. In geom_boxplot():
- lower whisker = smallest observation greater than or equal to lower
hinge - 1.5 * IQR
- lower hinge/bottom line of box part of boxplot = 25% quantile
- middle = median, 50% quantile
- upper hinge/top line of box part of boxplot = 75% quantile
- upper whisker = largest observation less than or equal to upper
hinge + 1.5 * IQR
alkaloids %>%
ggplot(aes(x = Class, y = Tomatine)) +
geom_boxplot()

One reason why this is really importantly different from the bar plot
is look at the number of outliers we are seeing for Wild Cherry. You
don’t capture this at all with the median/mean bar plots.
Because of the scale of this data, it might be beneficial to log
transform the y-axis.
alkaloids %>%
ggplot(aes(x = Class, y = Tomatine)) +
geom_boxplot() +
scale_y_continuous(trans = "log10") # or scale_y_log10()
## Warning: Transformation introduced infinite values in continuous y-axis
## Warning: Removed 3 rows containing non-finite values (stat_boxplot).

geom_jitter() is a shortcut for
geom_point(position = "jitter"), but is common enough that
the shortcut exists. It is often nice to jitter on top of a boxplot.
Note, if you don’t want the outliers from geom_boxplot() to
be plotted twice, you should indicate
outlier.shape = NA.
alkaloids %>%
ggplot(aes(x = Class, y = Tomatine)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter() +
scale_y_continuous(trans = "log10") # or scale_y_log10()
## Warning: Transformation introduced infinite values in continuous y-axis
## Transformation introduced infinite values in continuous y-axis
## Warning: Removed 3 rows containing non-finite values (stat_boxplot).
## Warning: Removed 3 rows containing missing values (geom_point).

Jittering introduces a small amount of variation into your points so
they’re easier to see. A width of 0 is no horizontal
jitter. A height of 0 is no vertical jitter. Typically you
don’t want veritcal jitter so that the points retain their fidelity on
the y-axis (which is where their concentration is plotted). I basically
always use geom_jitter(height = 0) for plots where I want
to retain y-axis fidelity.
alkaloids %>%
ggplot(aes(x = Class, y = Tomatine)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter(height = 0) +
scale_y_continuous(trans = "log10") # or scale_y_log10()
## Warning: Transformation introduced infinite values in continuous y-axis
## Transformation introduced infinite values in continuous y-axis
## Warning: Removed 3 rows containing non-finite values (stat_boxplot).
## Warning: Removed 3 rows containing missing values (geom_point).

We could also look at these distribution more like histograms and it
provides to us some additional information. When coupled with faceting,
this can be very powerful.
alkaloids %>%
ggplot(aes(x = Tomatine)) +
geom_histogram(bins = 75) + # default is bins = 30
scale_x_continuous(trans = "log10") +
facet_wrap(vars(Class))
## Warning: Transformation introduced infinite values in continuous x-axis
## Warning: Removed 3 rows containing non-finite values (stat_bin).

alkaloids %>%
ggplot(aes(x = Tomatine)) +
geom_density() +
scale_x_continuous(trans = "log10") +
facet_wrap(vars(Class))
## Warning: Transformation introduced infinite values in continuous x-axis
## Warning: Removed 3 rows containing non-finite values (stat_density).

I really like the function geom_density_ridges() which
is a part of the ggplot add-on package ggridges. It allows
you to create ridgeline plots to show distributes in a single
non-faceted plot.
library(ggridges) # for ridgeline plots
library(scales) # for comma format
alkaloids %>%
filter(Tomatine != 0) %>%
ggplot(aes(x = Tomatine, y = Class)) +
geom_density_ridges(alpha = 0.5) +
scale_x_continuous(trans = "log10", labels = comma) +
labs(x = "Alpha-tomatine content, µg/100g fresh weight",
y = "",
title = "Distribution of Alpha-Tomatine Content Across 107 Accessions \nof Tomato Grown Across 3 Environments")

You can also use the function geom_density_ridges()
which will allow you to easily map quantiles or other functions on top
of your ridges.
alkaloids %>%
filter(Tomatine != 0) %>%
ggplot(aes(x = Tomatine, y = Class)) +
stat_density_ridges(alpha = 0.5,
quantile_lines = TRUE,
quantiles = 2) + # break into 2 groups, therefore median
scale_x_continuous(trans = "log10", labels = comma) +
labs(x = "Alpha-tomatine content, µg/100g fresh weight",
y = "",
title = "Distribution of Alpha-Tomatine Content Across 107 Accessions \nof Tomato Grown Across 3 Environments",
caption = "Black line represents tomato class median concent")
## Picking joint bandwidth of 0.198

Changing class labels
I am bothered by the fact that S. pimpinellifolium (a species of wild
tomato) is not indicated in italics. We don’t want to italicize all of
the Class labels, just S. pimpinellifolium. Let’s
fix that.
We can start by creating a vector of the labels how we want them to
appear in the plot.
class_labels <- c("Cultivated Processing",
"Cultivated Cherry",
"Wide Cross Hybrid",
"Wild Cherry",
expression(italic("S. pimpinellifolium")))
class_labels
## expression("Cultivated Processing", "Cultivated Cherry", "Wide Cross Hybrid",
## "Wild Cherry", italic("S. pimpinellifolium"))
Then we can use one of the scale_*() functions to change
our y-axis scale labels to how we want them to be.
alkaloids %>%
filter(Tomatine != 0) %>%
ggplot(aes(x = Tomatine, y = Class)) +
geom_density_ridges(alpha = 0.5) +
scale_x_continuous(trans = "log10", labels = comma) +
scale_y_discrete(labels = class_labels) +
labs(x = "Alpha-tomatine content, µg/100g fresh weight",
y = "",
title = "Distribution of Alpha-Tomatine Content Across 107 Accessions \nof Tomato Grown Across 3 Environments")
## Picking joint bandwidth of 0.198

If for example your variables were mapped to color or
fill, you could do this using
scale_color_manual() or scale_fill_manual(),
respectively.
Another ggplot extension package ggdist has cool geoms
you can integrate into ggplots to visualize distributions. I think these
work better than geom_dotplot().
Sometimes using geom_jitter() when you have a lot of
data points can look a bit messy. I think in this case, using
geom_dots() works very well. The default orientation is is
layout = "bin"
library(ggdist)
##
## Attaching package: 'ggdist'
## The following objects are masked from 'package:ggridges':
##
## scale_point_color_continuous, scale_point_color_discrete,
## scale_point_colour_continuous, scale_point_colour_discrete,
## scale_point_fill_continuous, scale_point_fill_discrete,
## scale_point_size_continuous
alkaloids %>%
filter(Tomatine != 0) %>%
ggplot(aes(x = Tomatine, y = Class)) +
geom_dots() +
scale_x_continuous(trans = "log10", labels = comma) +
scale_y_discrete(labels = class_labels) +
labs(x = "Alpha-tomatine, µg/100 g fresh weight",
y = "",
title = "Distribution of alkaloid content found among \ntomatoes of different classes")

You can really change the feel of the plot by changing the
orientation between horizontal and vertical. If you want to use the
orientation layout = "swarm" you need the package
ggbeeswarm. This is also a nice package that performs
similarly to ggdist but has less functionality which is why
I’m covering ggdist here.
library(ggbeeswarm) # required for layout = "swarm"
alkaloids %>%
filter(Tomatine != 0) %>%
ggplot(aes(x = Class, y = Tomatine)) +
geom_dots(side = "both", layout = "swarm") + # requires ggbeeswarm
scale_x_discrete(labels = class_labels) +
scale_y_continuous(trans = "log10", labels = comma) +
labs(x = "",
y = "Alpha-tomatine, µg/100 g fresh weight",
title = "Distribution of alkaloid content found among tomatoes of different classes")

You can also use stat_dotsinterval() which will by
default add the median and the interquartile range (though you can
change exactly what you want to be displayed).
alkaloids %>%
filter(Tomatine != 0) %>%
ggplot(aes(x = Class, y = Tomatine)) +
stat_dotsinterval(side = "both") +
scale_x_discrete(labels = class_labels) +
scale_y_continuous(trans = "log10", labels = comma) +
labs(x = "",
y = "Alpha-tomatine, µg/100 g fresh weight",
title = "Distribution of alkaloid content found among tomatoes of different classes")

Don’t forget we can keep layering. We can always map other aethetics
to our plot (e.g. shape = as.factor(Year), and we include
as.factor() because Year is a character
datatype).
alkaloids %>%
filter(Tomatine != 0) %>%
ggplot(aes(x = Class, y = Tomatine, shape = as.factor(Year))) +
scale_y_continuous(trans = "log10", labels = comma) +
geom_dots(side = "both") +
theme_ggdist() +
theme(legend.position = c(.18, .99),
legend.justification = c("right", "top"),
legend.box.just = "right",
legend.box.background = element_rect(size=0.5),
legend.box.margin = margin(5, 5, 5, 5)) +
labs(shape = "Year",
y = "Alpha-tomatine (µg/100 g fresh weight)")

LS0tCnRpdGxlOiAiVW5kZXJzdGFuZGluZyBEYXRhIERpc3RyaWJ1dGlvbnMiCmF1dGhvcjogIkplc3NpY2EgQ29vcGVyc3RvbmUiCmRhdGU6ICI5LzI3LzIwMjIiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDQKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdGhlbWU6IGZsYXRseQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKYGBge3Igc3VtbWFyeSBzdGF0cywgZmlnLmFsdCA9ICJBIGNhcnRvb24gc2hvd2luZyBhIGNsaXAgYXJ0IHN0eWxlIGJhciBncmFwaCwgb24gdGhlIGxlZnQgd2hlcmUgYWxsIHRoZSBkYXRhIHBvaW50IGRvdHMgYXJlIGhlbGQgYXQgdGhlIGJvdHRvbSBieSBhIG5ldCwgYW5kIG9uZSBvbiB0aGUgcmlnaHQgd2hlcmUgdGhlIGZpZGVsaXR5IG9mIHRoZSBkYXRhcG9pbnRzIGFyZSBzaG93bi4gVGhlIGNlbnRlciBzYXlzICdhcmUgeW91ciBzdW1tYXJ5IHN0YXRpc3RpY3MgaGlkaW5nIHNvbWV0aGluZyBpbnRlcmVzdGluZz8nIiwgZmlnLmNhcD0gIkZpZ3VyZSBmcm9tIFtBbGxpc29uIEhvcnN0XShodHRwczovL2dpdGh1Yi5jb20vYWxsaXNvbmhvcnN0L3N0YXRzLWlsbHVzdHJhdGlvbnMvYmxvYi9tYWluL290aGVyLXN0YXRzLWFydHdvcmsvc3VtbWFyeV9zdGF0aXN0aWNzLnBuZykiLCBvdXQud2lkdGggPSAiNzAlIiwgZmlnLmFsaWduID0gImNlbnRlciIsIGVjaG8gPSBGQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltZy9zdW1tYXJ5X3N0YXRpc3RpY3MucG5nIikKYGBgCgoKIyMgSW50cm9kdWN0aW9uCgpXZSB3aWxsIHdpbGwgYnVpbGRpbmcgb24gb3VyIGxlc3NvbiBvbiBbZ2dwbG90MTAxXSgyXzA0X2dncGxvdC8wNF9nZ3Bsb3QuaHRtbCkgYW5kIFtnZ3Bsb3QxMDJdKDJfMDVfdGhlbWVzX2xhYmVsc19mYWNldHMvMDVfZ2dwbG90MTAyLmh0bWwpIHdoaWNoIGZvY3VzZWQgb24gYW4gb3ZlcmFsbCB1bmRlcnN0YW5kaW5nIG9mIHRoZSBncmFtbWFyIG9mIGdyYXBoaWNzLCBiYXNpYyBzeW50YXgsIGFkZGluZyBkYXRhLCBhZXN0aGV0aWMgbWFwcGluZ3MsIGdlb21zLCBmYWNldHMsIHNjYWxlcywgbGFiZWxzLCBhbmQgdGhlbWVzLiBUb2RheSB3ZSBhcmUgZ29pbmcgdG8gYXBwbHkgd2hhdCB3ZSBsZWFybmVkIHRvd2FyZHMgdHJ5aW5nIHRvIGJldHRlciB1bmRlcnN0YW5kaW5nIG91ciB1bmRlcmx5aW5nIGRhdGEgZGlzdHJpYnV0aW9ucy4KCk9mdGVuLCB3ZSB0aGluayBhYm91dCBmaWd1cmUgZ2VuZXJhdGlvbiBhcyB0aGUgbGFzdCBwYXJ0IG9mIHRoZSBzY2llbnRpZmljIHByb2Nlc3MsIHNvbWV0aGluZyB5b3UgZG8gYXMgeW91IHByZXBhcmUgYSBtYW51c2NyaXB0IGZvciBwdWJsaWNhdGlvbi4gSSBob3BlIHRvIGNvbnZpbmNlIHlvdSB0aGF0IGV4cGxvcmluZyB5b3VyIGRhdGEsIGFuZCBtYWtpbmcgZXhwbG9yYXRvcnkgcGxvdHMgaXMgYSBjcml0aWNhbCBwYXJ0IG9mIHRoZSBkYXRhIGFuYWx5c2lzIGFuZCBpbnRlcnByZXRhdGlvbiBwcm9jZXNzLgoKYGBge3IgZGF0YSBleHBsb3JhdGlvbiwgZmlnLmFsdCA9ICJBIGdyb3VwIG9mIGZ1enp5IHJvdW5kIG1vbnN0ZXJzIHdpdGggYmlub2N1bGFycywgYmFja3BhY2tzIGFuZCBndWlkZSBib29rcyBsb29raW5nIHVwIGEgZ3JhcGhzIGZseWluZyBhcm91bmQgd2l0aCB3aW5ncyAobGlrZSBiaXJkZXJzLCBidXQgd2l0aCBleHBsb3JhdG9yeSBkYXRhIHZpc3VhbGl6YXRpb25zKS4gU3R5bGl6ZWQgdGV4dCByZWFkcyDigJxnZ3Bsb3QyOiB2aXN1YWwgZGF0YSBleHBsb3JhdGlvbi7igJ0iLCBmaWcuY2FwPSAiRmlndXJlIGZyb20gW0FsbGlzb24gSG9yc3RdKGh0dHBzOi8vZ2l0aHViLmNvbS9hbGxpc29uaG9yc3Qvc3RhdHMtaWxsdXN0cmF0aW9ucykiLCBvdXQud2lkdGggPSAiNzAlIiwgZmlnLmFsaWduID0gImNlbnRlciIsIGVjaG8gPSBGQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltZy9nZ3Bsb3QyX2V4cGxvcmF0b3J5LnBuZyIpCmBgYAoKIyMjIExvYWQgbGlicmFyaWVzIGFuZCBkYXRhCkJlZm9yZSB3ZSBnZXQgc3RhcnRlZCwgbGV0J3MgbG9hZCBvdXIgbGlicmFyaWVzLgoKYGBge3IgbGlicmFyaWVzLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKVG9kYXkgd2UgYXJlIHVzaW5nIHJlYWwgcmVzZWFyY2ggZGF0YSBmcm9tIG15IGdyb3VwLiBXZSB3aWxsIGJlIHJlYWRpbmcgaW4gdGhlIHN1cHBsZW1lbnRhcnkgZGF0YSBmcm9tIGEgW3BhcGVyXShodHRwczovL2Fjc2Vzcy5vbmxpbmVsaWJyYXJ5LndpbGV5LmNvbS9kb2kvZnVsbC8xMC4xMDAyL3RwZzIuMjAxOTIpIHdyaXR0ZW4gYnkgTWljaGFlbCBEemFrb3ZpY2gsIGFuZCBwdWJsaXNoZWQgaW4gVGhlIFBsYW50IEdlbm9tZS4gVGhlIGRhdGEgaXMgcHJlc2VudCBpbiBhIEV4Y2VsIHdvcmtzaGVldCwgc28gd2Ugd2lsbCB1c2UgdGhlIGZ1bmN0aW9uIGByZWFkX2V4Y2VsKClgIGZyb20gdGhlIHRpZHl2ZXJzZSAoYnV0IG5vdCBjb3JlIHRpZHl2ZXJzZSkgcGFja2FnZSBgcmVhZHhsYC4gV2Ugd2FudCB0byBpbXBvcnQgU3VwcGxlbWVudGFsIFRhYmxlIDEuIFlvdSBjYW4gaW5kaWNhdGUgd2hpY2ggc2hlZXQgeW91IHdhbnQgdG8gaW1wb3J0IGluIHRoZSBhcmd1bWVudHMgdG8gYHJlYWRfZXhjZWwoKWAuCgpgYGB7ciByZWFkLWRhdGF9CmFsa2Fsb2lkcyA8LSByZWFkeGw6OnJlYWRfZXhjZWwoInRwZzIyMDE5Mi1zdXAtMDAwMi1zdXBtYXQueGxzeCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hlZXQgPSAiUzEgUmF3IERhdGEgRGl2ZXJzaXR5IFBhbmVsIikKYGBgCgpgYGB7ciBoZWFkLWFsa2Fsb2lkc30Ka25pdHI6OmthYmxlKGhlYWQoYWxrYWxvaWRzKSkKYGBgCgpUaGlzIGRhdGFzZXQgaGFzIDYwNSBvYnNlcnZhdGlvbnMsIHdpdGggZGF0YSBhYm91dCBkaWZmZXJlbnQgc3Rlcm9pZGFsIGFsa2Fsb2lkcyBpbiB0aGUgZnJ1aXRzIG9mIGRpZmZlcmVudCB0b21hdG8gZ2VybXBsYXNtIGdyb3duIGluIDMgbG9jYXRpb25zIGFjcm9zcyAyIHllYXJzLiBUaGVyZSBpcyBhbHNvIHNvbWUgb3RoZXIgbWV0YWRhdGEgdG9vLgoKIyMgR2VvbXMgZm9yIGRpc3RyaWJ1dGlvbnMKCiMjIyBbYGdlb21fY29sKClgXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvZ2VvbV9iYXIuaHRtbCkKCk9mdGVuLCBwZW9wbGUgdXNlIGJhciBjaGFydHMsIHJlcHJlc2VudGluZyB0aGUgaGVpZ2h0IG9yIHRoZSBsZW5ndGggb2YgdGhlIGJhciBhcyBwcm9wb3J0aW9uYWwgdG8gdGhlIGF2ZXJhZ2UgdmFsdWUgdGhhdCBpdCByZXByZXNlbnRzLiBUaGVzZSBjaGFydHMgYXJlIHNvbWV0aW1lcyBjYWxsZWQgZHluYW1pdGUgcGxvdHMgYmVjYXVzZSB0aGV5IHJlc2VtYmxlICh3aGVuIHRoZXkgaGF2ZSBhbiBlcnJvciBiYXIgd2l0aCB3aGlza2VyKSB0aG9zZSBjYXJ0b29uIHN0eWxlIGR5bmFtaXRlIHN0aWNrcy4gUG93IQoKSG93ZXZlciwgdGhlc2UgYmFyIGNoYXJ0cywgZXZlbiBpZiB5b3UgYWRkIGEgc3RhbmRhcmQgZGV2aWF0aW9uL2Vycm9yLCByZWFsbHkgY2FuIGhpZGUgdGhlIHRydWUgZGlzdHJpYnV0aW9uIG9mIHlvdXIgZGF0YSwgYW5kIGZvciB0aGlzIHJlYXNvbiwgSSBhbmQgW290aGVyc10oaHR0cHM6Ly9zaW1wbHlzdGF0aXN0aWNzLm9yZy9wb3N0cy8yMDE5LTAyLTIxLWR5bmFtaXRlLXBsb3RzLW11c3QtZGllLykgaG9wZSB5b3UgZG9u4oCZdCBzZWxlY3QgdG8gbWFrZSB0aGVtLgoKQXNpZGU6IFlvdSBtYXkgYmUgdGhpbmtpbmcgIkplc3MgeW91IGFza2VkIHVzIHRvIG1ha2Ugb25lIG9mIHRoZXNlIGluIE1vZHVsZSAyIGhvbWV3b3JrIiBhbmQgSSBkaWQgYnV0IGFsc28gdGhhdCB3YXMgYSBsaXR0bGUgZGlmZmVyZW50LiBUaGUgcGxvdCBJIGFza2VkIHlvdSB0byBtYWtlIHNob3dzIHRoZSBudW1iZXIgb2YgZGVncmVlcyBhd2FyZGVkLCBhIHZhbHVlIGZvciB3aGljaCB0aGVyZSByZWFsbHkgaXMgbm8gZGlzdHJpYnV0aW9uLiBTbyBpbiB0aGF0IGNhc2Ugd2UgYXJlIHVzaW5nIGEgYmFyIHBsb3QgdG8gc2hvdyBzb21ldGhpbmcgZGlmZmVyZW50IHRoYW4gYSBiYXIgcGxvdCB3aGljaCBpcyBtZWFudCB0byBzaG93IHNvbWVob3cgYW4gYXZlcmFnZS9tZWRpYW4gYW5kIGRpc3RyaWJ1dGlvbi4KCkkgaG9wZSBhZnRlciB0b2RheSwgeW91IHNlZSB0aGF0IHRoZXJlIGlzIGFsd2F5cyBhIGJldHRlciBjaGFydCB0eXBlIHRvIG1ha2UgdGhhbiBhIGJhciBjaGFydC4gQnV0IEkgd2lsbCBzaG93IHlvdSBob3cgdG8gbWFrZSB0aGVtIGFueXdheS4KCkJlZm9yZSB3ZSBwbG90LCBsZXTigJlzIGNhbGN1bGF0ZSBzb21lIHN1bW1hcnkgc3RhdGlzdGljcyBzbyB3ZSBrbm93IHdoYXQgd2Ugc2hvdWxkIGV4cGVjdC4KCmBgYHtyIHRvbWF0aW5lLXN1bW1hcnl9CmFsa2Fsb2lkcyAlPiUKICBncm91cF9ieShDbGFzcykgJT4lCiAgc3VtbWFyaXplKG1lYW5fdG9tYXRpbmUgPSBtZWFuKFRvbWF0aW5lKSkKYGBgCgpgYGB7ciBnZW9tLWNvbC13cm9uZ30KIyB0aGlzIGlzIHdyb25nIGJ1dCBhbiBlYXN5IG1pc3Rha2UgdG8gbWFrZQojIHRoaXMgaXMgbm90IHdoYXQgd2Ugd2FudAphbGthbG9pZHMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gQ2xhc3MsIHkgPSBUb21hdGluZSkpICsKICBnZW9tX2NvbCgpCmBgYAoKSnVzdCBjYWxsaW5nIGBnZW9tX2NvbCgpYCBkb2VzIG5vdCBnaXZlIHVzIHdoYXQgd2Ugd2FudC4gTG9vayBhdCB0aGUgeS1heGlzIHNjYWxlIGFuZCBob3cgb3V0IG9mIGxpbmUgdGhpcyBpcyB3aXRoIG91ciBzdW1tYXJ5IHN0YXRpc3RpY3MuIFRoZSByZWFzb24gZm9yIHRoaXMgaXMgdGhhdCBgZ2VvbV9jb2woKWAgZGVmYXVsdHMgdG8gYHBvc2l0aW9uID0gInN0YWNrImAgd2hpY2ggd2lsbCBqdXN0IHN1bSB0aGUgYWxrYWxvaWQgY29udGVudCBhY3Jvc3MgYWxsIHRoZSBvYnNlcnZhdGlvbnMuIEV2ZW4gY2hhbmdpbmcgdG8gICBgcG9zaXRpb24gPSAiaWRlbnRpdHkiYCBkb2VzIG5vdCB3b3JrLiBUaGlzIGlzIGJlY2F1c2Ugd2UgYXJlIHBsb3R0aW5nIGEgdHJhbnNmb3JtYXRpb24gb2YgdGhlIGRhdGEgKGNhbGN1bGF0aW9uIG9mIHRoZSBtZWFuKSB3aGljaCB0aGVzZSBnZW9tcyBhcmUgbm90IGRvaW5nLgoKV2UgY2FuIGNhbGN1bGF0ZSBtYW51YWxseSBieSBnZW5lcmF0aW5nIHRoZSBzdW1tYXJ5IHZhbHVlcyBhbmQgdGhlbiBwaXBpbmcgdGhhdCBpbnRvIG91ciBnZ3Bsb3QgY2FsbC4KYGBge3IgZ2VvbS1jb2wtbWFudWFsLXN1bW1hcnl9CmFsa2Fsb2lkcyAlPiUKICBncm91cF9ieShDbGFzcykgJT4lCiAgc3VtbWFyaXplKG1lYW5fdG9tYXRpbmUgPSBtZWFuKFRvbWF0aW5lKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gQ2xhc3MsIHkgPSBtZWFuX3RvbWF0aW5lKSkgKwogIGdlb21fY29sKCkKYGBgCgojIyMgW2BzdGF0X3N1bW1hcnkoKWBdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9zdGF0X3N1bW1hcnkuaHRtbCkKCkFuIGVhc2llciB3YXkgdG8gZG8gdGhpcyB3b3VsZCBiZSBqdXN0IHdpdGggYHN0YXRfc3VtbWFyeSgpYCwgd2hpY2ggZG9lcyBub3QgcmVxdWlyZSB0aGUgY2FsY3VsYXRpb24gb2Ygc3VtbWFyeSBzdGF0aXN0aWMgZmlyc3QuCmBgYHtyIGdlb20tY29sLXN0YXQtc3VtbWFyeX0KYWxrYWxvaWRzICU+JQogIGdncGxvdChhZXMoeCA9IENsYXNzLCB5ID0gVG9tYXRpbmUpKSArCiAgc3RhdF9zdW1tYXJ5KGZ1biA9ICJtZWFuIiwgZ2VvbSA9ICJiYXIiKQpgYGAKCiMjIyMgUmVvcmRlcmluZyB4LXZhcmlhYmxlcwpOb3RlIGluIHRoZXNlIHBsb3RzIHRoZSBvcmRlcmluZyBvZiB0aGUgeC1heGlzIGNhdGVnb3JpZXMgLS0gdGhleSBhcmUgYWxwaGFiZXRpY2FsLiBUaGlzIGlzIHRoZSBnZ3Bsb3QgZGVmYXVsdC4gVGhlcmUgYXJlIG1hbnkgcmVhc29ucyB3aHkgdGhpcyBtaWdodCBub3QgYmUgdGhlIG1vc3QgY29tcGVsbGluZyBvcmRlcmluZyBmb3IgeW91ciBkYXRhLiBZb3UgbWF5IHdhbnQgdG8gb3JkZXIgZnJvbSBsb3dlc3QgdG8gaGlnaGVzdCBtZWFuLCBvciBpbiB0aGlzIGNhc2UsIEkgd2FudCB0byBvcmRlciB0aGUgdG9tYXRvZXMgZnJvbSBtb3N0IGN1bHRpdmF0ZWQgb24gdGhlIGxlZnQsIHRvIG1vc3Qgd2lsZCBvbiB0aGUgcmlnaHQsIHNpbmNlIHRoaXMgaXMgdGhlIHByZXZhaWxpbmcgdGhlbWUgb2Ygb3VyIHBhcGVyLiAKCldlIGNhbiBkbyB0aGlzIGluIHR3byB3YXlzOgoKU2ltcGx5IHJlb3JkZXIgdGhlIHBsb3QuIApgYGB7ciByZW9yZGVyLXBsb3R9CiMgc2V0IHdoYXQgdGhlIG9yZGVyIGlzCmFsa2Fsb2lkc19vcmRlciA8LSBjKCJDdWx0aXZhdGVkIFByb2Nlc3NpbmciLAogICAgICAgICAgICAgICAgICAgICAiQ3VsdGl2YXRlZCBDaGVycnkiLAogICAgICAgICAgICAgICAgICAgICAiV2lkZSBDcm9zcyBIeWJyaWQiLAogICAgICAgICAgICAgICAgICAgICAiV2lsZCBDaGVycnkiLAogICAgICAgICAgICAgICAgICAgICAiUy4gcGltcGluZWxsaWZvbGl1bSIpCgojIHBsb3QgYW5kIHJlLWxldmVsIHdpdGhpbiBhZXMoKQphbGthbG9pZHMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmFjdG9yKENsYXNzLCBsZXZlbHMgPSBhbGthbG9pZHNfb3JkZXIpLCB5ID0gVG9tYXRpbmUpKSArCiAgc3RhdF9zdW1tYXJ5KGZ1biA9ICJtZWFuIiwgZ2VvbSA9ICJiYXIiKQpgYGAKCkNoYW5nZSB0aGUgbGV2ZWxzIG9mIHRoZSBkYXRhIHNvIHRoZSByZW9yZGVyaW5nIGhhcHBlbnMgdG8gZXZlcnkgcGxvdCBpbiB0aGUgZnV0dXJlLgpgYGB7ciByZWxldmVsbGluZ30KIyB3aGF0IHR5cGUgb2YgdmFyaWFibGUgaXMgY2xhc3M/CmNsYXNzKGFsa2Fsb2lkcyRDbGFzcykKCiMgY29udmVydCB0byBmYWN0b3IsIGFuZCBzZXQgbGV2ZWxzCmFsa2Fsb2lkcyRDbGFzcyA8LSBmYWN0b3IoYWxrYWxvaWRzJENsYXNzLAogICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkN1bHRpdmF0ZWQgUHJvY2Vzc2luZyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ3VsdGl2YXRlZCBDaGVycnkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIldpZGUgQ3Jvc3MgSHlicmlkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJXaWxkIENoZXJyeSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUy4gcGltcGluZWxsaWZvbGl1bSIpKQpgYGAKCmBgYHtyIHBsb3QtYWZ0ZXItcmVsZXZlbGxpbmd9CmFsa2Fsb2lkcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBDbGFzcywgeSA9IFRvbWF0aW5lKSkgKwogIHN0YXRfc3VtbWFyeShmdW4gPSAibWVhbiIsIGdlb20gPSAiYmFyIikKYGBgCgpNeSB0ZW5kZW5jeSB3b3VsZCBiZSB0byByZS1sZXZlbCB0aGUgZGF0YSBpZiBJIGFsd2F5cyB3YW50IHRvIHVzZSB0aGUgc2FtZSBvcmRlciwgYW5kIGp1c3QgcmUtbGV2ZWwgdGhlIHBsb3QgaWYgSSBvbmx5IHdhbnQgdG8gZG8gdGhpcyBvbmNlIG9yIHR3aWNlLgoKIyMjIFtgZ2VvbV9ib3hwbG90KClgXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvZ2VvbV9ib3hwbG90Lmh0bWwpCgpBIGJveHBsb3QgaGFzIHRoZSBiZW5lZml0IG9mIHNob3dpbmcgeW91IG1vcmUgdGhhbiB0aGUgbWVkaWFuIGFuZCB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uLCBzbyB5b3UgY2FuIGJldHRlciBzZWUgdGhlIHRydWUgZGlzdHJpYnV0aW9uIG9mIHlvdXIgZGF0YS4gSW4gYGdlb21fYm94cGxvdCgpYDoKCi0gbG93ZXIgd2hpc2tlciA9IHNtYWxsZXN0IG9ic2VydmF0aW9uIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byBsb3dlciBoaW5nZSAtIDEuNSAqIElRUgotIGxvd2VyIGhpbmdlL2JvdHRvbSBsaW5lIG9mIGJveCBwYXJ0IG9mIGJveHBsb3QgPSAyNSUgcXVhbnRpbGUKLSBtaWRkbGUgPSBtZWRpYW4sIDUwJSBxdWFudGlsZQotIHVwcGVyIGhpbmdlL3RvcCBsaW5lIG9mIGJveCBwYXJ0IG9mIGJveHBsb3QgPSA3NSUgcXVhbnRpbGUKLSB1cHBlciB3aGlza2VyID0gbGFyZ2VzdCBvYnNlcnZhdGlvbiBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gdXBwZXIgaGluZ2UgKyAxLjUgKiBJUVIKCmBgYHtyIGdlb20tYm94cGxvdH0KYWxrYWxvaWRzICU+JQogIGdncGxvdChhZXMoeCA9IENsYXNzLCB5ID0gVG9tYXRpbmUpKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCgpPbmUgcmVhc29uIHdoeSB0aGlzIGlzIHJlYWxseSBpbXBvcnRhbnRseSBkaWZmZXJlbnQgZnJvbSB0aGUgYmFyIHBsb3QgaXMgbG9vayBhdCB0aGUgbnVtYmVyIG9mIG91dGxpZXJzIHdlIGFyZSBzZWVpbmcgZm9yIFdpbGQgQ2hlcnJ5LiBZb3UgZG9uJ3QgY2FwdHVyZSB0aGlzIGF0IGFsbCB3aXRoIHRoZSBtZWRpYW4vbWVhbiBiYXIgcGxvdHMuCgpCZWNhdXNlIG9mIHRoZSBzY2FsZSBvZiB0aGlzIGRhdGEsIGl0IG1pZ2h0IGJlIGJlbmVmaWNpYWwgdG8gbG9nIHRyYW5zZm9ybSB0aGUgeS1heGlzLgpgYGB7ciBnZW9tLWJveHBsb3QtbG9nfQphbGthbG9pZHMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gQ2xhc3MsIHkgPSBUb21hdGluZSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIikgIyBvciBzY2FsZV95X2xvZzEwKCkKYGBgCgojIyMgW2BnZW9tX2ppdHRlcigpYF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2dlb21faml0dGVyLmh0bWwpCgpgZ2VvbV9qaXR0ZXIoKWAgaXMgYSBzaG9ydGN1dCBmb3IgYGdlb21fcG9pbnQocG9zaXRpb24gPSAiaml0dGVyIilgLCBidXQgaXMgY29tbW9uIGVub3VnaCB0aGF0IHRoZSBzaG9ydGN1dCBleGlzdHMuIEl0IGlzIG9mdGVuIG5pY2UgdG8gaml0dGVyIG9uIHRvcCBvZiBhIGJveHBsb3QuIE5vdGUsIGlmIHlvdSBkb24ndCB3YW50IHRoZSBvdXRsaWVycyBmcm9tIGBnZW9tX2JveHBsb3QoKWAgdG8gYmUgcGxvdHRlZCB0d2ljZSwgeW91IHNob3VsZCBpbmRpY2F0ZSBgb3V0bGllci5zaGFwZSA9IE5BYC4KCmBgYHtyIGdlb20taml0dGVyfQphbGthbG9pZHMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gQ2xhc3MsIHkgPSBUb21hdGluZSkpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArCiAgZ2VvbV9qaXR0ZXIoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIikgIyBvciBzY2FsZV95X2xvZzEwKCkKYGBgCgpKaXR0ZXJpbmcgaW50cm9kdWNlcyBhIHNtYWxsIGFtb3VudCBvZiB2YXJpYXRpb24gaW50byB5b3VyIHBvaW50cyBzbyB0aGV5J3JlIGVhc2llciB0byBzZWUuIEEgd2lkdGggb2YgYDBgIGlzIG5vIGhvcml6b250YWwgaml0dGVyLiBBIGhlaWdodCBvZiBgMGAgaXMgbm8gdmVydGljYWwgaml0dGVyLiBUeXBpY2FsbHkgeW91IGRvbid0IHdhbnQgdmVyaXRjYWwgaml0dGVyIHNvIHRoYXQgdGhlIHBvaW50cyByZXRhaW4gdGhlaXIgZmlkZWxpdHkgb24gdGhlIHktYXhpcyAod2hpY2ggaXMgd2hlcmUgdGhlaXIgY29uY2VudHJhdGlvbiBpcyBwbG90dGVkKS4gSSBiYXNpY2FsbHkgYWx3YXlzIHVzZSBgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMClgIGZvciBwbG90cyB3aGVyZSBJIHdhbnQgdG8gcmV0YWluIHktYXhpcyBmaWRlbGl0eS4KCmBgYHtyIGdlb20taml0dGVyLWhlaWdodC0wfQphbGthbG9pZHMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gQ2xhc3MsIHkgPSBUb21hdGluZSkpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArCiAgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMCkgKwogIHNjYWxlX3lfY29udGludW91cyh0cmFucyA9ICJsb2cxMCIpICMgb3Igc2NhbGVfeV9sb2cxMCgpCmBgYAoKIyMjIFtgZ2VvbV9oaXN0b2dyYW0oKWBdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9nZW9tX2hpc3RvZ3JhbS5odG1sKQoKV2UgY291bGQgYWxzbyBsb29rIGF0IHRoZXNlIGRpc3RyaWJ1dGlvbiBtb3JlIGxpa2UgaGlzdG9ncmFtcyBhbmQgaXQgcHJvdmlkZXMgdG8gdXMgc29tZSBhZGRpdGlvbmFsIGluZm9ybWF0aW9uLiBXaGVuIGNvdXBsZWQgd2l0aCBmYWNldGluZywgdGhpcyBjYW4gYmUgdmVyeSBwb3dlcmZ1bC4KCmBgYHtyIGhpc3RvZ3JhbX0KYWxrYWxvaWRzICU+JQogIGdncGxvdChhZXMoeCA9IFRvbWF0aW5lKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSA3NSkgKyAjIGRlZmF1bHQgaXMgYmlucyA9IDMwCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIikgKwogIGZhY2V0X3dyYXAodmFycyhDbGFzcykpCmBgYAoKYGBge3IgZGVuc2l0eX0KYWxrYWxvaWRzICU+JQogIGdncGxvdChhZXMoeCA9IFRvbWF0aW5lKSkgKwogIGdlb21fZGVuc2l0eSgpICsKICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMTAiKSArCiAgZmFjZXRfd3JhcCh2YXJzKENsYXNzKSkKYGBgCgojIyMgW2BnZzpyaWRnZXM6Omdlb21fZGVuc2l0eV9yaWRnZXMoKWBdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9nZ3JpZGdlcy92ZXJzaW9ucy8wLjUuMi90b3BpY3MvZ2VvbV9kZW5zaXR5X3JpZGdlcykKCkkgcmVhbGx5IGxpa2UgdGhlIGZ1bmN0aW9uIGBnZW9tX2RlbnNpdHlfcmlkZ2VzKClgIHdoaWNoIGlzIGEgcGFydCBvZiB0aGUgZ2dwbG90IGFkZC1vbiBwYWNrYWdlIGBnZ3JpZGdlc2AuIEl0IGFsbG93cyB5b3UgdG8gY3JlYXRlIHJpZGdlbGluZSBwbG90cyB0byBzaG93IGRpc3RyaWJ1dGVzIGluIGEgc2luZ2xlIG5vbi1mYWNldGVkIHBsb3QuCmBgYHtyIGdlb20tZGVuc2l0eS1yaWRnZWxpbmUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGdncmlkZ2VzKSAjIGZvciByaWRnZWxpbmUgcGxvdHMKbGlicmFyeShzY2FsZXMpICMgZm9yIGNvbW1hIGZvcm1hdAoKYWxrYWxvaWRzICU+JQogIGZpbHRlcihUb21hdGluZSAhPSAwKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBUb21hdGluZSwgeSA9IENsYXNzKSkgKwogIGdlb21fZGVuc2l0eV9yaWRnZXMoYWxwaGEgPSAwLjUpICsKICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMTAiLCBsYWJlbHMgPSBjb21tYSkgKwogIGxhYnMoeCA9ICJBbHBoYS10b21hdGluZSBjb250ZW50LCDCtWcvMTAwZyBmcmVzaCB3ZWlnaHQiLAogICAgICAgeSA9ICIiLAogICAgICAgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIEFscGhhLVRvbWF0aW5lIENvbnRlbnQgQWNyb3NzIDEwNyBBY2Nlc3Npb25zIFxub2YgVG9tYXRvIEdyb3duIEFjcm9zcyAzIEVudmlyb25tZW50cyIpCmBgYAoKWW91IGNhbiBhbHNvIHVzZSB0aGUgZnVuY3Rpb24gW2BnZW9tX2RlbnNpdHlfcmlkZ2VzKClgXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvZ2dyaWRnZXMvdmVyc2lvbnMvMC41LjMvdG9waWNzL3N0YXRfZGVuc2l0eV9yaWRnZXMpIHdoaWNoIHdpbGwgYWxsb3cgeW91IHRvIGVhc2lseSBtYXAgcXVhbnRpbGVzIG9yIG90aGVyIGZ1bmN0aW9ucyBvbiB0b3Agb2YgeW91ciByaWRnZXMuCgpgYGB7ciBzdGF0LWRlbnNpdHktcmlkZ2VsaW5lfQphbGthbG9pZHMgJT4lCiAgZmlsdGVyKFRvbWF0aW5lICE9IDApICU+JQogIGdncGxvdChhZXMoeCA9IFRvbWF0aW5lLCB5ID0gQ2xhc3MpKSArCiAgc3RhdF9kZW5zaXR5X3JpZGdlcyhhbHBoYSA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgIHF1YW50aWxlX2xpbmVzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIHF1YW50aWxlcyA9IDIpICsgIyBicmVhayBpbnRvIDIgZ3JvdXBzLCB0aGVyZWZvcmUgbWVkaWFuCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIiwgbGFiZWxzID0gY29tbWEpICsKICBsYWJzKHggPSAiQWxwaGEtdG9tYXRpbmUgY29udGVudCwgwrVnLzEwMGcgZnJlc2ggd2VpZ2h0IiwKICAgICAgIHkgPSAiIiwKICAgICAgIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBBbHBoYS1Ub21hdGluZSBDb250ZW50IEFjcm9zcyAxMDcgQWNjZXNzaW9ucyBcbm9mIFRvbWF0byBHcm93biBBY3Jvc3MgMyBFbnZpcm9ubWVudHMiLAogICAgICAgY2FwdGlvbiA9ICJCbGFjayBsaW5lIHJlcHJlc2VudHMgdG9tYXRvIGNsYXNzIG1lZGlhbiBjb25jZW50IikKYGBgCgoKIyMjIyBDaGFuZ2luZyBjbGFzcyBsYWJlbHMKSSBhbSBib3RoZXJlZCBieSB0aGUgZmFjdCB0aGF0IFMuIHBpbXBpbmVsbGlmb2xpdW0gKGEgc3BlY2llcyBvZiB3aWxkIHRvbWF0bykgaXMgbm90IGluZGljYXRlZCBpbiBpdGFsaWNzLiBXZSBkb24ndCB3YW50IHRvIGl0YWxpY2l6ZSBhbGwgb2YgdGhlIGBDbGFzcyBsYWJlbHNgLCBqdXN0ICpTLiBwaW1waW5lbGxpZm9saXVtKi4gTGV0J3MgZml4IHRoYXQuCgpXZSBjYW4gc3RhcnQgYnkgY3JlYXRpbmcgYSB2ZWN0b3Igb2YgdGhlIGxhYmVscyBob3cgd2Ugd2FudCB0aGVtIHRvIGFwcGVhciBpbiB0aGUgcGxvdC4KYGBge3Igc2V0LWNsYXNzLWxhYmVsc30KY2xhc3NfbGFiZWxzIDwtIGMoIkN1bHRpdmF0ZWQgUHJvY2Vzc2luZyIsIAogICAgICAgICAgICAgICAgICAiQ3VsdGl2YXRlZCBDaGVycnkiLAogICAgICAgICAgICAgICAgICAiV2lkZSBDcm9zcyBIeWJyaWQiLCAKICAgICAgICAgICAgICAgICAgIldpbGQgQ2hlcnJ5IiwKICAgICAgICAgICAgICAgICAgZXhwcmVzc2lvbihpdGFsaWMoIlMuIHBpbXBpbmVsbGlmb2xpdW0iKSkpCgpjbGFzc19sYWJlbHMKYGBgCgpUaGVuIHdlIGNhbiB1c2Ugb25lIG9mIHRoZSBgc2NhbGVfKigpYCBmdW5jdGlvbnMgdG8gY2hhbmdlIG91ciB5LWF4aXMgc2NhbGUgbGFiZWxzIHRvIGhvdyB3ZSB3YW50IHRoZW0gdG8gYmUuCmBgYHtyIHJpZGdlbGluZS1sYWJlbHN9CmFsa2Fsb2lkcyAlPiUKICBmaWx0ZXIoVG9tYXRpbmUgIT0gMCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gVG9tYXRpbmUsIHkgPSBDbGFzcykpICsKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFscGhhID0gMC41KSArCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIiwgbGFiZWxzID0gY29tbWEpICsKICBzY2FsZV95X2Rpc2NyZXRlKGxhYmVscyA9IGNsYXNzX2xhYmVscykgKwogIGxhYnMoeCA9ICJBbHBoYS10b21hdGluZSBjb250ZW50LCDCtWcvMTAwZyBmcmVzaCB3ZWlnaHQiLAogICAgICAgeSA9ICIiLAogICAgICAgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIEFscGhhLVRvbWF0aW5lIENvbnRlbnQgQWNyb3NzIDEwNyBBY2Nlc3Npb25zIFxub2YgVG9tYXRvIEdyb3duIEFjcm9zcyAzIEVudmlyb25tZW50cyIpCmBgYAoKSWYgZm9yIGV4YW1wbGUgeW91ciB2YXJpYWJsZXMgd2VyZSBtYXBwZWQgdG8gYGNvbG9yYCBvciBgZmlsbGAsIHlvdSBjb3VsZCBkbyB0aGlzIHVzaW5nIGBzY2FsZV9jb2xvcl9tYW51YWwoKWAgb3IgYHNjYWxlX2ZpbGxfbWFudWFsKClgLCByZXNwZWN0aXZlbHkuCgojIyMgW2BnZ2Rpc3RgIGZ1bmN0aW9uc10oaHR0cHM6Ly9tanNrYXkuZ2l0aHViLmlvL2dnZGlzdC9pbmRleC5odG1sKQoKQW5vdGhlciBnZ3Bsb3QgZXh0ZW5zaW9uIHBhY2thZ2UgYGdnZGlzdGAgaGFzIGNvb2wgZ2VvbXMgeW91IGNhbiBpbnRlZ3JhdGUgaW50byBnZ3Bsb3RzIHRvIHZpc3VhbGl6ZSBkaXN0cmlidXRpb25zLiBJIHRoaW5rIHRoZXNlIHdvcmsgYmV0dGVyIHRoYW4gYGdlb21fZG90cGxvdCgpYC4KClNvbWV0aW1lcyB1c2luZyBgZ2VvbV9qaXR0ZXIoKWAgd2hlbiB5b3UgaGF2ZSBhIGxvdCBvZiBkYXRhIHBvaW50cyBjYW4gbG9vayBhIGJpdCBtZXNzeS4gSSB0aGluayBpbiB0aGlzIGNhc2UsIHVzaW5nIGBnZW9tX2RvdHMoKWAgd29ya3MgdmVyeSB3ZWxsLiBUaGUgZGVmYXVsdCBvcmllbnRhdGlvbiBpcyBpcyBgbGF5b3V0ID0gImJpbiJgCmBgYHtyIGdnZGlzdCBzdGF0X2RvdHMgYm90aH0KbGlicmFyeShnZ2Rpc3QpCgphbGthbG9pZHMgJT4lCiAgZmlsdGVyKFRvbWF0aW5lICE9IDApICU+JQogIGdncGxvdChhZXMoeCA9IFRvbWF0aW5lLCB5ID0gQ2xhc3MpKSArICAKICBnZW9tX2RvdHMoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIiwgbGFiZWxzID0gY29tbWEpICsgCiAgc2NhbGVfeV9kaXNjcmV0ZShsYWJlbHMgPSBjbGFzc19sYWJlbHMpICsKICBsYWJzKHggPSAiQWxwaGEtdG9tYXRpbmUsIMK1Zy8xMDAgZyBmcmVzaCB3ZWlnaHQiLAogICAgICAgeSA9ICIiLAogICAgICAgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIGFsa2Fsb2lkIGNvbnRlbnQgZm91bmQgYW1vbmcgXG50b21hdG9lcyBvZiBkaWZmZXJlbnQgY2xhc3NlcyIpCmBgYAoKWW91IGNhbiByZWFsbHkgY2hhbmdlIHRoZSBmZWVsIG9mIHRoZSBwbG90IGJ5IGNoYW5naW5nIHRoZSBvcmllbnRhdGlvbiBiZXR3ZWVuIGhvcml6b250YWwgYW5kIHZlcnRpY2FsLiBJZiB5b3Ugd2FudCB0byB1c2UgdGhlIG9yaWVudGF0aW9uIGBsYXlvdXQgPSAic3dhcm0iYCB5b3UgbmVlZCB0aGUgcGFja2FnZSBgZ2diZWVzd2FybWAuIFRoaXMgaXMgYWxzbyBhIG5pY2UgcGFja2FnZSB0aGF0IHBlcmZvcm1zIHNpbWlsYXJseSB0byBgZ2dkaXN0YCBidXQgaGFzIGxlc3MgZnVuY3Rpb25hbGl0eSB3aGljaCBpcyB3aHkgSSdtIGNvdmVyaW5nIGBnZ2Rpc3RgIGhlcmUuCgpgYGB7ciBnZ2Rpc3Qgc3RhdF9kb3RzIGJvdGggc3dhcm19CmxpYnJhcnkoZ2diZWVzd2FybSkgIyByZXF1aXJlZCBmb3IgbGF5b3V0ID0gInN3YXJtIgoKYWxrYWxvaWRzICU+JQogIGZpbHRlcihUb21hdGluZSAhPSAwKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBDbGFzcywgeSA9IFRvbWF0aW5lKSkgKwogIGdlb21fZG90cyhzaWRlID0gImJvdGgiLCBsYXlvdXQgPSAic3dhcm0iKSArICMgcmVxdWlyZXMgZ2diZWVzd2FybQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gY2xhc3NfbGFiZWxzKSArCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIiwgbGFiZWxzID0gY29tbWEpICsgCiAgbGFicyh4ID0gIiIsCiAgICAgICB5ID0gIkFscGhhLXRvbWF0aW5lLCDCtWcvMTAwIGcgZnJlc2ggd2VpZ2h0IiwKICAgICAgIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBhbGthbG9pZCBjb250ZW50IGZvdW5kIGFtb25nIHRvbWF0b2VzIG9mIGRpZmZlcmVudCBjbGFzc2VzIikKYGBgCgpZb3UgY2FuIGFsc28gdXNlIGBzdGF0X2RvdHNpbnRlcnZhbCgpYCB3aGljaCB3aWxsIGJ5IGRlZmF1bHQgYWRkIHRoZSBtZWRpYW4gYW5kIHRoZSBpbnRlcnF1YXJ0aWxlIHJhbmdlICh0aG91Z2ggeW91IGNhbiBjaGFuZ2UgZXhhY3RseSB3aGF0IHlvdSB3YW50IHRvIGJlIGRpc3BsYXllZCkuCmBgYHtyIGdnZGlzdCBzdGF0X2RvdHNpbnRlcnZhbH0gIAphbGthbG9pZHMgJT4lCiAgZmlsdGVyKFRvbWF0aW5lICE9IDApICU+JQogIGdncGxvdChhZXMoeCA9IENsYXNzLCB5ID0gVG9tYXRpbmUpKSArCiAgc3RhdF9kb3RzaW50ZXJ2YWwoc2lkZSA9ICJib3RoIikgKwogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gY2xhc3NfbGFiZWxzKSArCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIiwgbGFiZWxzID0gY29tbWEpICsgCiAgbGFicyh4ID0gIiIsCiAgICAgICB5ID0gIkFscGhhLXRvbWF0aW5lLCDCtWcvMTAwIGcgZnJlc2ggd2VpZ2h0IiwKICAgICAgIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBhbGthbG9pZCBjb250ZW50IGZvdW5kIGFtb25nIHRvbWF0b2VzIG9mIGRpZmZlcmVudCBjbGFzc2VzIikKYGBgCgpEb24ndCBmb3JnZXQgd2UgY2FuIGtlZXAgbGF5ZXJpbmcuIFdlIGNhbiBhbHdheXMgbWFwIG90aGVyIGFldGhldGljcyB0byBvdXIgcGxvdCAoZS5nLiBgc2hhcGUgPSBhcy5mYWN0b3IoWWVhcilgLCBhbmQgd2UgaW5jbHVkZSBgYXMuZmFjdG9yKClgIGJlY2F1c2UgYFllYXJgIGlzIGEgY2hhcmFjdGVyIGRhdGF0eXBlKS4KYGBge3IgZ2VvbS1kb3RzIHllYXIgYnkgc2hhcGUgYW5kIGxlZ2VuZCBib3h9CmFsa2Fsb2lkcyAlPiUKICBmaWx0ZXIoVG9tYXRpbmUgIT0gMCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gQ2xhc3MsIHkgPSBUb21hdGluZSwgc2hhcGUgPSBhcy5mYWN0b3IoWWVhcikpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIiwgbGFiZWxzID0gY29tbWEpICsgCiAgZ2VvbV9kb3RzKHNpZGUgPSAiYm90aCIpICsKICB0aGVtZV9nZ2Rpc3QoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYyguMTgsIC45OSksCiAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSBjKCJyaWdodCIsICJ0b3AiKSwKICAgICAgICBsZWdlbmQuYm94Lmp1c3QgPSAicmlnaHQiLAogICAgICAgIGxlZ2VuZC5ib3guYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChzaXplPTAuNSksCiAgICAgICAgbGVnZW5kLmJveC5tYXJnaW4gPSBtYXJnaW4oNSwgNSwgNSwgNSkpICsKICBsYWJzKHNoYXBlID0gIlllYXIiLAogICAgICAgeSA9ICJBbHBoYS10b21hdGluZSAowrVnLzEwMCBnIGZyZXNoIHdlaWdodCkiKSAKYGBgCgojIyBJbiBjbGFzcwoKSW4gY2xhc3MsIHdlIHdpbGwgcHJhY3RpY2UgdXNpbmcgZ2dwbG90IGFuZCBpbnZlc3RpZ2F0aW5nIGRhdGEgZGlzdHJpYnV0aW9ucy4KCllvdSBjYW4gZmluZCB0aGUgaW4gY2xhc3MgY29udGVudCBbaGVyZV0oM18wNl9kaXN0cmlidXRpb25zLzA2X2Rpc3RyaWJ1dGlvbnNfcmVjaXRhdGlvbi5odG1sKS4KCiMjIyBVc2VmdWwgcmVzb3VyY2VzOgoKLSAgIFtgZ2dwbG90MmAgY2hlYXRzaGVldF0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3JzdHVkaW8vY2hlYXRzaGVldHMvbWFpbi9kYXRhLXZpc3VhbGl6YXRpb24ucGRmKQotICAgW2BnZ3Bsb3QyYCBkb2N1bWVudGF0aW9uXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy8pCi0gICBbZ2dwbG90MjogZWxlZ2FudCBncmFwaGljcyBmb3IgZGF0YSBhbmFseXNpcyBieSBIYWRsZXkgV2lja2hhbV0oaHR0cHM6Ly9nZ3Bsb3QyLWJvb2sub3JnL2luZGV4Lmh0bWwpCi0gICBbQSByZWFsbHkgY29tcGVoZW5zaXZlIGxpc3Qgb2YgcmVzb3VyY2VzIGNvbXBpbGVkIGJ5IEVyaWsgR2FobmVyIExhcnNlbl0oaHR0cHM6Ly9naXRodWIuY29tL2VyaWtnYWhuZXIvYXdlc29tZS1nZ3Bsb3QyKQotICAgW2BnZ3JpZGdlc2BdKGh0dHBzOi8vd2lsa2VsYWIub3JnL2dncmlkZ2VzLykKLSAgIFtgZ2dkaXN0YF0oaHR0cHM6Ly9tanNrYXkuZ2l0aHViLmlvL2dnZGlzdC9pbmRleC5odG1sKQotICAgW2BnZ2JlZXN3YXJtYF0oKQoKCi0gUGFzdCBnZ3Bsb3QgQ29kZSBDbHViczoKCiAgKiBbVmlzdWFsaXppbmcgRGF0YSBieSBNaWNoYWVsIEJyb2VdKGh0dHBzOi8vYmlvZGFzaC5naXRodWIuaW8vY29kZWNsdWIvMDRfZ2dwbG90Mi8pCiAgKiBbZ2dwbG90IHJvdW5kIDIgYnkgbWVdKGh0dHBzOi8vYmlvZGFzaC5naXRodWIuaW8vY29kZWNsdWIvMDVfZ2dwbG90LXJvdW5kLTIvKQogICogW0ZhY2V0aW5nLCBtdWx0aS1wbG90cywgYW5kIGFuaW1hdGluZ10oaHR0cHM6Ly9iaW9kYXNoLmdpdGh1Yi5pby9jb2RlY2x1Yi8xMF9mYWNldGluZy1hbmltYXRpbmcvKQogICogW1Zpc3VhbGl6aW5nIERhdGEgYnkgTWljaGFlbCBCcm9lIGEgc2Vjb25kIG9uZV0oaHR0cHM6Ly9iaW9kYXNoLmdpdGh1Yi5pby9jb2RlY2x1Yi9zMDJlMDZfZ2dwbG90Mi8pCiAgKiBbZ2dwbG90IHJvdW5kIDIgYSBzZWNvbmQgb25lIGJ5IG1lXShodHRwczovL2Jpb2Rhc2guZ2l0aHViLmlvL2NvZGVjbHViL3MwMmUwN19nZ3Bsb3QyX3BhcnQyLykKICAKCg==